//
//	MiscTeeConnectorInspector.m -- The inspector for the multiple connector
//								   object that's the inspectee.
//
//		Written by David Fedchenko.  Copyright 1994 by David Fedchenko.
//				Version 1.0  All rights reserved.
//
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	

#import "MiscTeeConnectorInspector.h"
#import "MiscTee.subproj/MiscTeeConnector.h"
#import "MiscDimpleCell.h"
#import "StringList.bproj/StringList.h"
#import <objc/objc-runtime.h>

@implementation MiscTee (ConnectionsInspector)

-(const char *) getConnectInspectorClassName
    {
	return "MiscTeeConnectorInspector";
    }

-(NXImage *) getIBImage
    {
    id	idBundle;
    id	idImage;
	char buf[MAXPATHLEN + 1];
	
	idBundle = [NXBundle bundleForClass:[MiscTeeConnectorInspector class]];
    [idBundle getPath:buf forResource:"MiscTee" ofType:"tiff"];
    idImage = [[NXImage alloc] initFromFile:buf];
	
	return idImage;
    }

@end

@implementation MiscTeeConnectorInspector

- init
    {
    char buf[MAXPATHLEN + 1];
    id idBundle;
	NXStream * stream;
	char * pch;
	int len;
	int maxlen;
	
    [super init];
	
    idBundle = [NXBundle bundleForClass:[self class]];
    [idBundle getPath:buf forResource:"MiscTeeConnectorInspector"
			  ofType:"nib"];
    [NXApp loadNibFile:buf owner:self withNames:NO fromZone:[self zone]];
    
	connectorList = [[List alloc] init];
	idConnector = nil;
	
	// Load in the StringList class for the following stuff.  We have to
	// do it by hand since the user may have the StringList palette loaded
	// which means we'll wind up with a rld() problem.  So we load it if
	// it doesn't already exist.  This will hopefully keep the problems
	// from arising.
	if (objc_lookUpClass("StringList") == nil)
		{
		id idBundleClass;
		
		[idBundle getPath:buf forResource:"StringList" ofType:"bundle"];
		idBundleClass = [[NXBundle allocFromZone:[self zone]] 
							initForDirectory:buf];
		if ([idBundleClass classNamed:"StringList"] == nil)
			{
			// we can't create the list to set it up, so bail out of here now
			NXLogError("MiscTeeInspector couldn't load StringList class.");
			return self;
			}
		}
	
	// Load the names of selectors that have a single id argument that
	// are not actions.  This is to keep the noise down in the display.
	// The file is kept in the .palette wrapper so the list can easily
	// be edited by somebody who cares about it.
	idNonActions = [[objc_lookUpClass("StringList") alloc] init];
    [idBundle getPath:buf forResource:"NonActions" ofType:"txt"];
	stream = NXMapFile(buf, NX_READONLY);
	if (stream)
		{
		NXGetMemoryBuffer(stream, &pch, &len, &maxlen);
		[idNonActions addDelimitedStrings:pch delimiters:"\n"
					  ifAbsent:NO sorted:YES at:0];				
		NXClose(stream);
		}
	
    return self;
    }

- free
	{
	[connectorList free];
	
	return [super free];
	}

- awakeFromNib
	{
	[outletBrowser setDoubleAction:@selector(ok:)];
	[outletBrowser setCellClass:[MiscDimpleCell class]];
	
	// I'm not sure why I have to do the following, but if I don't the
	// matrix is filled with NXBrowserCells which won't work here.
	[[[outletBrowser matrixInColumn:0] setCellClass:[MiscDimpleCell class]]
									   setPrototype:nil];
	return self;
	}

- ok:sender
    {
    [self findConnection];

    if ([okButton tag] == 0)
        {
		idConnector = [[MiscTeeConnector alloc] init];
		[idConnector setDestination:[NXApp connectDestination]];
		[idConnector setSource:object];
		if ([outletBrowser selectedColumn] == 1)
			{
			const char * sz;
			
			sz = [[outletBrowser selectedCell] stringValue];
			[idConnector setAction:sel_getUid(sz)];
			}
		else
			{
			[idConnector setAction:NULL];
			[outletBrowser reloadColumn:1];
			}
		
		[[NXApp activeDocument] addConnector:idConnector];
        }
    else if ([okButton tag] == 1)
        {
		[[NXApp activeDocument] removeConnector:idConnector];
		[idConnector free];
		idConnector = nil;
        }
	
    [self revert:self];
	
    return [super ok:sender];
    }

- revert:sender
    {
    int i = [self findConnection];
	
	if (i == -1 && [connectorList count])
		{
		i = 0;
		idConnector = [connectorList objectAt:0];
		}
	
	if (idConnector)
		{
		[[okButton setTitle:"Disconnect"] setTag:1];
		[okButton setEnabled:YES];
		}
	else if (i > -1)
		{
		// a new connection
		[[okButton setTitle:"Connect"] setTag:0];
		[okButton setEnabled:YES];
		i = [connectorList count];
		}
	
	[window disableFlushWindow];
	if (![outletBrowser isLoaded])
		{
		[outletBrowser loadColumnZero];
		}
	else
		{
		[outletBrowser reloadColumn:0];
		[[outletBrowser matrixInColumn:0] selectCellAt:i :0];
		}
	
	if ([outletBrowser matrixInColumn:1] == nil)
		{
		[outletBrowser addColumn];
		}
	else
		{
		[outletBrowser reloadColumn:1];
		[[outletBrowser matrixInColumn:1] selectCellAt:-1 :-1];
		}
	[window reenableFlushWindow];
	[window flushWindowIfNeeded];
	
    if (sender == self)
		{
		return self;
		}
    else
		{
		return [super revert:sender];
		}
    }

- (BOOL)wantsButtons
    {
    return YES;
    }

- refreshList
	{
	[connectorList empty];
	
	[[NXApp activeDocument] listConnectors:connectorList
							forSource:object
							filterClass:[MiscTeeConnector class]];

	return self;
	}

-(int) findConnection
    {
	int	i = -1;
	int	c;
	id	idCon;
	
	[self refreshList];
    
	idConnector = nil;
	
	if ([NXApp isConnecting])
		{
		c = [connectorList count];
		for (i = 0; i < c; i++)
			{
			idCon = [connectorList objectAt:i];
			if ([idCon destination] == [NXApp connectDestination])
				{
				idConnector = idCon;
				break;
				}
			}
		}
	
    return i;
    }

- clickOutlet:sender
	{
	int i;
	
	i = [[sender matrixInColumn:0] selectedRow];
	
	if (i < [connectorList count])
		{
		idConnector = [connectorList objectAt:i];
		
		[NXApp displayConnectionBetween:[idConnector source]
									and:[idConnector destination]];
		}
	else
		{
		idConnector = nil;
		}
		
    return self;
	}

-(int) browser:sender fillMatrix:matrix inColumn:(int)column
	{
	int 	c = 0;
	int 	i;
	id		idDest;
	id		idCell;
	char	szName[500];
	
	if (column == 0)
		{
		[self refreshList];
		c = [connectorList count];
		[matrix renewRows:((idConnector == nil) ? c + 1 : c) cols:1];
		for (i = 0; i < c; i++)
			{
			idDest = [[connectorList objectAt:i] destination];
			idCell = [matrix cellAt:i :0];
			strcpy(szName, [idDest name]);
			[self cleanClassName:szName];
			[idCell setStringValue:szName];
			[idCell setLoaded:YES];
			if ([[connectorList objectAt:i] action])
				{
				[idCell setMarked:NO];
				[idCell setLeaf:NO];
				}
			else
				{
				[idCell setMarked:YES];
				}
			}
		
		if (idConnector == nil && [NXApp isConnecting])
			{
			idCell = [matrix cellAt:c :0];
			strcpy(szName, [[NXApp connectDestination] name]);
			[self cleanClassName:szName];
			[idCell setStringValue:szName];
			[[idCell setLoaded:YES] setLeaf:NO];
			c++;
			}
		
		[matrix sizeToCells];
		}
	else if (column == 1)
		{
		id	idActionList;
		id	idSelected = [[objc_lookUpClass("StringList") alloc] init];
		int count;
		SEL	selAction;
		const char * sz;
		
		if (idConnector || [NXApp isConnecting])
			{
			if ((idConnector == nil) || [idConnector action])
				{
				if (idConnector == nil)
					{
					strcpy(szName, [[[NXApp connectDestination] class] name]);
					}
				else
					{
					strcpy(szName, [[[idConnector destination] class] name]);
					}
				[self cleanClassName:szName];
				idActionList = [self getActions:szName];
				c = [idActionList count];
				
				[matrix renewRows:c cols:1];
				count = 0;
				for (i = 0; i < c ; i++)
					{
					selAction = *(SEL *)[idActionList elementAt:i];
					sz = sel_getName(selAction);
					if (![idNonActions stringExists:sz])
						{
						[idSelected addString:sz];
						count++;
						}
					}
				
				[idSelected sortStrings:self];
				count = [idSelected count];
				for (i = 0; i < count ; i++)
					{
					idCell = [matrix cellAt:i :0];
					[idCell setStringValue:[idSelected stringAt:i]];
					[[[idCell setLoaded:YES] setLeaf:YES] setMarked:NO];
					if ((idConnector != nil) && [idConnector action])
						{
						if (!strcmp(sel_getName([idConnector action]),
									[idSelected stringAt:i]))
							{
							[idCell setMarked:YES];
							}
						}
					}
				
				c = count;
				[matrix renewRows:c cols:1];
				}
			else
				{
				c = 0;
				[matrix renewRows:0 cols:1];
				}
			}
		
		[matrix sizeToCells];
		[[idSelected freeStrings] free];
		
		}
	
	return c;
	}

- cleanClassName:(char *)sz
	{
	char * pEnd = sz + strlen(sz);
	
	// rename a few specific classes that IB sneaks in the mix
	// it's looks like the only ones we have to worry about just have
	// "Template" stuck on the end.
	
	if (!strncmp("Template", pEnd - 8, 8))
		{
		*(pEnd - 8) = 0;
		}
	
	return self;
	}


void walk_method_list(struct objc_method_list * pMethods, id idStore)
	{
	const char * sz;
	int	i;
	
	for (i = 0; i < pMethods->method_count; i++)
		{
		if (!strcmp(pMethods->method_list[i].method_types, "@12@8:12@16"))
			{
			sz = sel_getName(pMethods->method_list[i].method_name);
			if (*sz != '_')
				{
				[idStore addElement:&pMethods->method_list[i].method_name];
				}
			}
		}
	
	
	return;
	}

- getActions:(const char *)szClassName
	{
	id	idStore;
	struct objc_method_list * pMethods;
	struct objc_class * pClass;
	
	idStore = [[Storage alloc] initCount:0
							   elementSize:sizeof(SEL)
							   description:@encode(SEL)];
	
	pClass = objc_lookUpClass(szClassName);
	while (pClass)
		{
		pMethods = pClass->methods;
		while (pMethods)
			{
			walk_method_list(pMethods, idStore);
			
			pMethods = pMethods->method_next;
			}
		pClass = pClass->super_class;
		}
	
	return idStore;
	}

@end
